<?php
/**
 * Глобальный класс управления материалами сайта
 * 
 * @category Phorm
 * @package Resource
 * @name /Phorm/Resource.php
 * @copyright phorm.ru
 * @since 30.11.2010
 * @author vladimir@kachalov.net
 */

class Phorm_Resource extends Phorm_Model {
	
	/**
	 * Id модуля материалов
	 * @var string
	 */
	private $_moduleid;
	
	/**
	 * Путь к папке с файлами конфигураций относительно /application/configs
	 * @var string
	 */
	private $_configpath;
	
	/**
	 * Массив конфигурации модуля материалов
	 * @var array
	 */
	public $settings = array();
	
	
	/**
	 * Конструктор
	 *
	 * @param string $moduleid Id модуля (файлы настроек должны быть размещены в папке /application/configs/resources/$moduleid)
	 */
	
	public function __construct($moduleid = null) {
		
		parent::__construct();
		
		$this->setModuleid($moduleid);
		$this->setConfig();
		
	}
	
	
	/**
	 * Устанавливает конфигурацию текущего модуля материалов сайта
	 *
	 * @return void
	 */
	
	public function setConfig() {
		
		if(is_null($this->_moduleid)) return false;
		
		$this->settings = Phorm_Config::xml($this->_configpath . '/settings.xml');
		
	}
	
	
	/**
	 * Устанавливает идентификатор текущего модуля материала, а заодно и путь к файлам конфигураций
	 *
	 * @param string $moduleid
	 */
	
	public function setModuleid($moduleid) {
		
		$this->_moduleid = $moduleid;
		
		$dir = 'resources/' . $this->_moduleid . '/' . $this->getModule();
		$this->_configpath = !is_null($moduleid) && is_dir(APPLICATION_PATH . '/configs/' . $dir) ? $dir : 'resources';
		
	}
	
	
	/**
	 * Возвращает идентификатор текущего модуля материала
	 *
	 * @return string
	 */
	
	public function getModuleid() {
		
		return $this->_moduleid;
		
	}
	
	
	/**
	 * Возвращает массив опций для построения таблицы Phorm_JQuery_DataGrid
	 *
	 * @param string $section Секция xml-файла
	 * @return array
	 */
	
	public function getGridOptions($section = 'index') {
		
		/**
		 * Расширяем конфигурацию на основе мастер-файла
		 */
		
		$files = array_unique(array(
			'resources/grids.xml',
			$this->_configpath . '/grids.xml'
		));
		
		$options = Phorm_Config::xml($files, $section);
		
        /**
         * Добавляем список разделов на поисковую форму
         */
        
		if(isset($options['search']['categoryid']))
			$options['search']['categoryid']['optiondata'] = $this->getCategoriesListByModule($this->_moduleid);
			
		return $options;
		
	}
	
	
	/**
	 * Возвращает список материалов
	 * 
	 * @param array $opions Массив опций
	 * 				groupby => categories Группирорвать материалы по разделу (Для каждого найденного раздела рекурсивно вызывается метод getResourcesList)
	 * 				categories => int | array Возвращает материалы по разделу. При группировке по разделам может быть только массивом
	 * 				categoryid => int Возвращает материалы по разделу (в некоторой степени дублирует categories)
	 * 				getdepends => true Возвращает связанные с текущим материалы (Текущий материал определяется на основе текущего запроса)
	 * 				tags => array Возвращает материалы по заданным тегам (Теги в виде Id)
	 * 				moduleid => string Возвращает материалы по заданному модулю (новости там, статьи... По умолчанию возвращаются материалы текущего модуля если он задан, если нет, то из всех)
	 * 				resources => array Возвращает материалы с определенными id. Передаем просто список значений
	 * 				fields => array Список возвращаемых столбцов из таблицы материалов + image - картинка для превью. По умолчанию возвращаются все
	 * 				pubdate_from => string Дата публикации с ... (значение должно быть форматированной датой)
	 * 				pubdate_to => string Дата публикации по ... (значение должно быть форматированной датой)
	 * 				orderby => category | pubdate | sortorder | alphabet Сортировка. 
	 * 							Для category - сортировка по положению в иерархии 
	 * 							Для pubdate - сортировка по дате публикации в обратном порядке
	 * 							Для sortorder - сортировка по параметру сортирвки в натуральном порядке (нули отодвигаются на последние места)
	 * 							Для alphabet - сортировка по названию материала в натуральном порядке
	 * 							По умолчанию материалы сортируются по дате публикации в обратном порядке
	 * 				sortField => resourceid | pubdate | resourcename | resourceuid | sortorder | visible | categoryname Дополнительная сортировка по полям
	 * 				sortOrder => asc | desc Направление сортировки. Работает только с sortField
	 * 
	 * @return mixed array|Zend_Db_Select
	 * 
	 * Кроме прочих данных всегда возвращается несуществующий столбец actions равный actions
	 * Его можно использовать в JS-гридах для отрисовки дополнительной колонки с действиями
	 */
	
	public function getResourcesList($options=array()) {
		
		/**
		 * Если опциями задана группировка и список разделов, делаем сгруппированную по разделам выборку
		 */
			
		if(isset($options['groupby']) && $options['groupby']=='categories' && isset($options['categories']) && is_array($options['categories']) && !empty($options['categories'])) {
				
			foreach ($options['categories'] as $k=>$v) $options['categories'][$k] = (int)$v;
			
			$select =  $this->_db->select()
					-> from('pw_categories')
					//-> where('isexist=?','yes')
					-> where('fullpath_exist=?','yes')
					-> where('categoryid IN ('.implode(',',$options['categories']).')')
					-> order('cleft');
					
			if($this->getModule()!='control') {
				$select -> where('fullpath_visible=?','yes');
			}
			
			$out = array();
				
			foreach ($this->_db->fetchAll($select) as $data) {
					
				$options['groupby'] = false;
				$options['categories'] = array($data['categoryid']);
					
				$out[$data['categoryid']] = $data;					
				$out[$data['categoryid']]['resources'] = $this->getResourcesList($options);	
									
			}
				
			return $out;
				
		}
		
		/**
		 * Получаем список материалов на основе переданных опций
		 */
		
		// Формируем вложенный запрос для первого изображения
		$imgsql = "(SELECT CONCAT(imagepath,'/',imageid,'.',imagetype) FROM pw_resources_media WHERE mediatype='image' AND resourceid=AA.resourceid ORDER BY sortorder LIMIT 1)";
		
		// Определяем возвращаемые столбцы
		if(isset($options['fields']) && is_array($options['fields'])) {
			
			if(isset($options['fields']['image'])) {
				$fields = array('image'=>$imgsql);
				unset($options['fields']['image']);
			} else {
				$fields = array();
			}
			$fields[] = new Zend_Db_Expr("SQL_CALC_FOUND_ROWS DISTINCT AA.".implode(",AA.",$options['fields']).",'actions' actions");
		
		} else {
			//$fields = array(new Zend_Db_Expr("SQL_CALC_FOUND_ROWS DISTINCT AA.*,'actions' actions"),'image'=>$imgsql);
			$fields = array(new Zend_Db_Expr("SQL_CALC_FOUND_ROWS DISTINCT AA.*,'actions' actions"));
			
		}
		
		// Формируем основной запрос
		$select =  $this->_db->select()
				-> from(array('AA'=>'pw_resources'),$fields)
				-> join(array('BB'=>'pw_resources_categories'), 'AA.resourceid=BB.resourceid', array())
				-> join(array('CC'=>'pw_categories'), 'BB.categoryid=CC.categoryid',array('categoryid','categoryname','fullpath'))
				-> joinInner(array('DD'=>'pw_resources_modules'),'AA.moduleid=DD.moduleid',array('modulename'));
				
		// Добавляем JOIN если нам нужны связанные с текущим материалы
		if(isset($options['getdepends']) && isset($this->_params['resourceinfo'])) {
			$select -> join(array('EE'=>'pw_resources_depends'), 
								'AA.resourceid IN(EE.resourceid1,EE.resourceid2) AND '.
								(int)$this->_params['resourceinfo']['resourceid'].' IN(EE.resourceid1,EE.resourceid2)')
					-> where('AA.resourceid!=?',$this->_params['resourceinfo']['resourceid']);
		}
		
		// Добавляем JOIN если нужна выборка по тегам
		if(isset($options['tags'])) {
			foreach ($options['tags'] as $k=>$v) $options['tags'][$k] = (int)$v;
			$select -> join(array('EE'=>'pw_resources_tags'),'AA.resourceid=EE.resourceid',array())
					-> where('EE.tagid IN('.implode(',',$options['tags']).')');
		}
		
		// Добавляем условие на существование материала
		$select -> where('AA.isexist=?','yes')
				-> where('AA.pubdate<=?',time());

		// Добавляем условие, если выборка для фронтенда
		if($this->getModule()!='control') {
			$select -> where('AA.visible=?','yes')
					-> where('CC.fullpath_visible=?','yes');
		}
		
		// Добавляем условие по модулю
		if(isset($options['moduleid'])) {
			$select -> where('AA.moduleid=?',$options['moduleid']);
		} elseif (!is_null($this->_moduleid)) {
			$select -> where('AA.moduleid=?',$this->_moduleid);
		}
		
		// Дата публикации
		if(isset($options['pubdate_from']) && trim($options['pubdate_from'])!='') {
			$select -> where('AA.pubdate>=?',strtotime($options['pubdate_from']));
		}
		
		if(isset($options['pubdate_to']) && trim($options['pubdate_to'])!='') {
			$select -> where('AA.pubdate<=?',strtotime($options['pubdate_to']));
		}
		
		// Добавляем условие по разделу
		if(isset($options['categoryid']) && $options['categoryid']>0) {
			$select -> where('CC.categoryid=?',$options['categoryid']);
		}
		
		// Если раздел задан в виде числа
		if(isset($options['categories']) && is_int($options['categories'])) {
			$select -> where('CC.categoryid=?',$options['categories']);
		}	

		// Если разделы заданы в виде массива
		if(isset($options['categories']) && is_array($options['categories']) && !empty($options['categories'])) {
			foreach ($options['categories'] as $k=>$v) $options['categories'][$k] = (int)$v;
			$select -> where('CC.categoryid IN ('.implode(',',$options['categories']).')');
		}
		
		// Добавляем условие по списку id материалов
		if(isset($options['resources']) && is_array($options['resources']) && !empty($options['resources'])) {
			foreach ($options['resources'] as $k=>$v) $options['resources'][$k] = (int)$v;
			$select -> where('AA.resourceid IN ('.implode(',',$options['resources']).')');
		}
		
		// Сортировка по разделу
		if(isset($options['orderby']) && $options['orderby']=='category') {
			$select -> order('CC.cleft')
					-> order('AA.pubdate DESC');
		}
		
		// Сортировка по дате публикации
		elseif(isset($options['orderby']) && $options['orderby']=='pubdate') {
			$select -> order('AA.pubdate DESC')
					-> order('CC.cleft');
		}
		
		// Сортировка по параметру сортировки
		elseif(isset($options['orderby']) && $options['orderby']=='sortorder') {
			$select -> order('IF(AA.sortorder=0,999999999,AA.sortorder)')
					-> order('AA.resourcename')
					-> order('AA.pubdate DESC')
					-> order('CC.cleft');
		}
		
		// Сортировка по алфавиту
		elseif(isset($options['orderby']) && $options['orderby']=='alphabet') {
			$select -> order('AA.resourcename')
					-> order('AA.pubdate DESC')
					-> order('CC.cleft');
		}
		
		// Другие сортировки (в частности, сортировки в гридах)
		elseif(isset($options['sortField']) && in_array($options['sortField'],array('resourceid','pubdate','resourcename','resourceuid','sortorder','visible'))) {
			
			$sortOrder = isset($options['sortOrder']) && $options['sortOrder']=='desc' ? ' DESC' : ' ASC';
			$select -> order('AA.'.$options['sortField'].$sortOrder);
			
		}
		
		// Сортировка по названию раздела
		elseif(isset($options['sortField']) && in_array($options['sortField'],array('categoryname'))) {
			
			$sortOrder = isset($options['sortOrder']) && strtolower($options['sortOrder'])=='desc' ? ' DESC' : ' ASC';
			$select -> order('CC.'.$options['sortField'].$sortOrder);
			
		}
		
		// Сортировка по умолчанию
		else {
			$select -> order('AA.pubdate DESC')
					-> order('CC.cleft');
		}
		
		// Устанавливаем количество отдаваемых строк
		if(isset($options['rowsperpage']) && isset($options['widgetid'])) {
			$select -> limitPage($this->getCurrentPage($options['widgetid']),(int)$options['rowsperpage']);
		} elseif (isset($options['rowsperpage'])) {
			$select -> limitPage($this->getCurrentPage(),(int)$options['rowsperpage']);
		} elseif (isset($options['widgetid'])) {
			$select -> limitPage($this->getCurrentPage($options['widgetid']),$this->getPerPage());
		} else {
			$select -> limitPage($this->getCurrentPage(),$this->getPerPage());
		}
		
		// Добавляем группировку
		$select -> group('AA.resourceid');
		
		// Если опциями задан возврат в виде объекта, возвращаем Zend_Db_Select
		if(isset($options['return_as']) && $options['return_as']=='select') {
			return $select;
		// Иначе возвращаем массив
		} else {
			return $this->_db->fetchAll($select);
		}
			
	}
	
	
	/**
	 * Возвращает базовую информацию о материале
	 * 
	 * @param string $identity Идентификатор материала (resourceid или resourceuid)
	 * @param array $options Дополнительные условия для выборки
	 * 
	 * @return array
	 * @todo Добавить поле creator, убрать выборку из лога, а выбирать инфо об авторе из таблицы пользователей
	 */
	
	public function getResourceInfo($identity,array $options=array()) {
		
		$select = $this->_db->select()
				-> from(array('AA'=>'pw_resources'),
						new Zend_Db_Expr("AA.*,(SELECT userid FROM pw_resources_log 
												WHERE resourceid=AA.resourceid AND actiontype='insert' 
												ORDER BY logid DESC LIMIT 1) creator"))
				-> where('(AA.resourceid=? OR AA.resourceuid=?)',$identity)
				-> where('AA.isexist=?','yes');
			
		// Вешаем условие на выборку по разделам	
		if(isset($options['categoryid'])) {
			$select -> join(array('BB'=>'pw_resources_categories'),'AA.resourceid=BB.resourceid')
					-> where('BB.categoryid=?',$options['categoryid']);
		}
		
		// Вешаем условие на выборку по модулю
		if(isset($options['moduleid'])) {
			$select -> where('AA.moduleid=?',$options['moduleid']);
		}
		
		// Вешаем условие на выборку для фронт-енда
		if($this->getModule()!='control') {
			$select -> where('AA.visible=?','yes')
					-> where('AA.pubdate<=?',time());
		}	
		
		// Выполняем запрос и получаем дополнительное инфо по материалу
		if($row = $this->_db->fetchRow($select)) {
			return $this->_getAdditionalInfo($row);
		}
				
		return false;
		
	}
	
	
	/**
	 * Возвращает базовую информацию о материале
	 *
	 * @param string $identity
	 * @return array
	 */
	
	public function getResourceBaseInfo($identity) {
		
		$select = $this->_db->select()
				-> from('pw_resources')
				-> where('(resourceid=? OR resourceuid=?)',$identity)
				-> where('isexist=?','yes');
				
		if($this->getModule()!="control") $select -> where('visible=?','yes');

		return $this->_db->fetchRow($select);	
		
	}
	
	
	/**
	 * Проверяет, может ли пользователь выполнить определенное действие над материалом в разделе
	 * 
	 * @param string $action Наименование действия ("view","add","edit","delete")
	 * @param mixed $category Id раздела или массив разделов 
	 * 				Массив разделов нужен при добавлении материала, мы обрабатываем все разделы
	 * 				и если хотя бы в одном из них есть права на добавление, возвращаем true.
	 * 				Если список разделов не передан, то используются разделы из текущего модуля
	 * @param int $creator Id создателя материала
	 *  
	 * @return boolean
	 */

	public function isAllowedAction($action, $category = null, $creator=-1) {
		
		$return = false;
		
		if(is_null($category)) $category = array_keys($this->getCategoriesListByModule($this->_moduleid));
		
		if(is_string($category)) $category = array($category);
		
		foreach ($category as $categoryid) {
				
			switch ($action) {
						
				case 'view':
								
					if(in_array($this->_usersession->UserData['acl']['categories'][$categoryid],range(300,399))
								&& $creator==$this->_usersession->UserData['userid']) {
									
						$return = true;
									
					} elseif(in_array($this->_usersession->UserData['acl']['categories'][$categoryid],range(400,1000))) {
									
						$return = true;
									
					}
								
					break;
							
				case 'add':
							
					if(in_array($this->_usersession->UserData['acl']['categories'][$categoryid],range(600,799))
								&& $creator==$this->_usersession->UserData['userid']) {
									
						$return = true;
									
					} elseif(in_array($this->_usersession->UserData['acl']['categories'][$categoryid],range(500,1000))) {
									
						$return = true;
									
					}
							
					break;
								
				case 'edit':
								
					if(in_array($this->_usersession->UserData['acl']['categories'][$categoryid],range(600,799))
								&& $creator==$this->_usersession->UserData['userid']) {
									
						$return = true;
									
					} elseif(in_array($this->_usersession->UserData['acl']['categories'][$categoryid],range(800,1000))) {
							
						$return = true;
									
					}
								
					break;
								
				case 'delete':
								
					if(in_array($this->_usersession->UserData['acl']['categories'][$categoryid],range(700,799))
								&& $creator==$this->_usersession->UserData['userid']) {
								
						$return = true;
									
					} elseif(in_array($this->_usersession->UserData['acl']['categories'][$categoryid],range(900,1000))) {
									
						$return = true;
									
					}
								
					break;
								
			}
		
		}
			
		return $return;
			
	}
	
	
	/**
	 * Возвращает форму добавления/редактирования материала
	 *
	 * @param string $section Секция xml-файла
	 * @return Phorm_Form
	 */
	
	public function getResourceForm($section = 'add') {
		
		/**
		 * Расширяем конфигурацию на основе мастер-файла
		 */
		
		$files = array_unique(array(
			'resources/forms.xml',
			$this->_configpath . '/forms.xml'
		));
		
		/**
		 * Добавляем Id модуля материалов для выборки разделов
		 */
		
		$opts = array(
			'elements' => array(
				'categories' => array(
					'options' => array(
						'moduleid' => $this->_moduleid
					)
				)
			)
		);
		
		$form = new Phorm_Form($files, $section, $opts);
		
		return $form;
		
	}

	
	/**
	 * Сохраняет определенные поля для списка материалов из массива пар
	 * Например, для групповых изменений значений в списке материалов (ну там скопом видимость менять и прочее...) 
	 * 
	 * @param array $data Массив пар (id_материала=>значение)
	 * @param string $column Обновляемая колонка
	 * @param string $filter Фильтр значений
	 *  
	 * @return boolean
	 * @todo Добавить логирование
	 */
		
	public function savePairs($data,$column,$filter='trim') {
		
		$this->_db->beginTransaction();
		
		try {
			
			foreach ($data as $resourceid=>$value) {		
				$this->_db->update('pw_resources',array($column=>$value),'resourceid='.(int)$resourceid);
				// Логирование !!!
			}
			
			$this->_db->commit();
			
			return true;
		
		} catch (Exception $e) {
			
			$this->_db->rollBack();	
			$this->_errors[] = $e->getMessage();
			
		}
		
		return false;
			
	}
	
	
	/**
	 * Сохраняет данные формы создания/редактирования материала
	 * 
	 * @param Phorm_Form $form Объект формы
	 * @param mixed $data Дополнительные данные формы. Если передана строка, то принимается как resourceid
	 * @param mixed $additionals Имя метода или массив имен для дополнительной обработки данных
	 * 							 Методы должны существовать в текущем классе (в т.ч. могут быть и в расширенном)
	 * 							 Используется для сохранения дополнительных данных в модулях материалов
	 * 							 Каждый из методов должен возвращать массив данных с модифицированными ключами 
	 * 							 @see Например, Phorm_Resource_Forms::saveFormInfo
	 * @param mixed $filters Имя метода или массив имен для предварительной фильтрации данных
	 * 						 Методы должны существовать в текущем классе (в т.ч. могут быть и в расширенном)
	 * 						 Каждый из методов должен возвращать массив данных с модифицированными ключами
	 * @see /application/configs/resources/forms.xml
	 * @return array | false
	 */
		
	public function saveResourceForm(Phorm_Form $form, $data = null, $additionals = null, $filters = null) {
		
		/**
		 * Получаем значения формы и объединяем их с массивом дополнительных данных
		 */
		
		if(!is_null($data) && is_string($data)) $data = array('resourceid'=>$data);
		$data = array_merge($form->getValues(),(array)$data);
		
		/**
		 * Предварительный фильтр значений
		 */
		
		if(!$data = $this->_filterFormInfo($data,$filters)) return false;
		$maindata = $this->getFilteredValuesByTableCols('pw_resources',$data);
		
		/**
		 * Сохранение данных
		 */
		
		$this->_db->beginTransaction();
		
		try {
			
			// Изменение существующей записи
			
			if(isset($data['resourceid'])) {
				
				$this->_db->update('pw_resources',$maindata,'resourceid='.(int)$data['resourceid']);
				$action = 'update';
				
			// Вставка новой записи
				
			} else {
				
				$this->_db->insert('pw_resources',$maindata);
				$data['resourceid'] = $this->_db->lastInsertId();
				$action = 'insert';
				
			}
			
			/**
			 * Дополнительные данные материала
			 */
			
			if(!$data = $this->_saveAdditionalInfo($data,$additionals)) {
				$this->_db->rollBack();
				return false;
			}
			
			/**
			 * Поисковый индекс
			 */
			
			$this->buildSearchIndex(array('indextitle' => $data['resourcename'],
										  'indextxt'   => $data['resourcetxt'],
										  'resourceid' => $data['resourceid']));
										  
			/**
			 * Логирование
			 */
			$this->setLog($action,
				array('url' => '/control/resource/edit/resourceid/' . (int)$data['resourceid'] . '/',
					  'name' => $data['resourcename'],
					  'tablename' => 'pw_resources',
					  'identity' => (int)$data['resourceid'],
					  'fieldname' => 'resourceid'));
			
			
			$this->_db->commit();						  
			return $data;
			
		} catch (Exception $e) {
			
			$this->_db->rollBack();	
			$this->_errors[] = $e->getMessage();
			$this->_errors[] = $e->getTraceAsString();
			
		}
		
		return false;
			
	}
	
	
	/**
	 * Удаляет материал сайта + заносит запись в лог
	 * 
	 * @param array $ResourceInfo Массив данных о материале
	 * 
	 * @return boolean
	 */
		
	function deleteResource($ResourceInfo) {
			
		$this->_db->beginTransaction();
		
		try {
			
			$this->_db->update('pw_resources',array('isexist'=>'no'),'resourceid='.(int)$ResourceInfo['resourceid']);

			/**
			 * Логирование
			 */
			$this->setLog('delete',
				array('url' => '/control/resource/edit/resourceid/' . (int)$ResourceInfo['resourceid'] . '/',
					  'name' => $ResourceInfo['resourcename'],
					  'tablename' => 'pw_resources',
					  'identity' => (int)$ResourceInfo['resourceid'],
					  'fieldname' => 'resourceid'));
			
			$this->_db->commit();
			
			return true;
			
		} catch (Exception $e) {
			
			$this->_db->rollBack();	
			$this->_errors[] = $e->getMessage();
			
		}
			
		return false;
			
	}
	
	
	/**
	 * Возвращает стартовые значения при создании материала на основе предыдущих записей
	 *
	 * @return array
	 * @todo Подумать над выборкой последней записи только текущего автора
	 */
	
	public function getHumanValues() {
		
		// Получаем самый свежий материал по текущему модулю + еще бы автора добавить...
		if($Last = $this->getResourcesList(array('moduleid'=>$this->_moduleid,'sortField'=>'resourceid','sortOrder'=>'desc','rowsperpage'=>1))) {
			if($Info = $this->getResourceInfo($Last[0]['resourceid'])) {
				$LastInfo = array(
					'widgetssetid' => $Info['widgetssetid'],
					'visible' => $Info['visible'],
					'categories' => $Info['categories'],
					'pubdate' => date('d.m.Y H:i'),
					'ratingallowed' => $Info['ratingallowed'],
					'commentsallowed' => $Info['commentsallowed'],
					'commentsshow' => $Info['commentsshow'],
					'commentsmoderate' => $Info['commentsmoderate'],
				);
				return $LastInfo;
			}
		}

		return false;
		
	}
	
	
	/**
	 * Возвращает информацию о заданном модуле материала
	 * 
	 * @param string $moduleid Id модуля
	 * 
	 * @return array
	 */
		
	public function getModuleInfo($moduleid) {
		
		if(!$moduleid || !is_string($moduleid)) return false;
		
		$select = $this->_db->select()
				-> from('pw_resources_modules')
				-> where('moduleid=?',$moduleid);

		return $this->_db->fetchRow($select);
			
	}
	
	
	/**
	 * Возвращает список существующих в системе модулей материалов
	 * 
	 * @return array
	 */
		
	public function getModulesList() {
		
		$select = $this->_db->select()
				-> from('pw_resources_modules')
				-> order('modulename');
			
		return $this->_db->fetchAll($select);
			
	}
	
	
	/**
	 * Возвращает список существующих в системе модулей материалов в виде массива пар moduleid=>modulename
	 * 
	 * @return array
	 */
		
	public function getModulesListAsPairs() {
		
		$select = $this->_db->select()
				-> from('pw_resources_modules',array('moduleid','modulename'))
				-> order('modulename');
			
		return $this->_db->fetchPairs($select);
			
	}
	
	
	/**
	 * Генерирует идентификатор материала на основе заголовка
	 * Выполняет также функцию уникализации идентификатора @see self::saveResourceForm
	 *
	 * @param string $str Строка для модификации
	 * @param int $exclude Id материала, который не надо учитывать при проверке уникальности
	 * @return string
	 * @todo Подумать насчет условия на существование материала, а также кирилические урлы. М.б. соль добавить (ид материала, например) для гарантированной уникализации
	 */
	
	public function getUidGenerated($str,$exclude = null) {
		
		$search = array('а','б','в','г','д','е','ё','ж','з','и','й','к','л','м','н','о','п','р','с','т','у','ф','х','ц','ч','ш','щ','ь','ы','ъ','э','ю','я');
		$replace = array('a','b','v','g','d','e','yo','zh','z','i','y','k','l','m','n','o','p','r','s','t','u','f','kh','c','ch','sh','sch','','i','','e','u','ya');
		$str = str_replace($search,$replace,trim(mb_strtolower($str)));
			
		$str = preg_replace('#[^-_\s0-9a-z]#u','',$str);
		$str = preg_replace('#\s+#u','_',$str);
		$str = substr($str,0,250);
		
		// Проверяем на уникальность в базе
		
		if(!is_null($exclude) && $exclude>0) {
			$select = $this -> _db->select()
						-> from('pw_resources',array('resourceuid'))
						-> where('resourceuid=?',$str)
						-> where('isexist=?','yes')
						-> where('resourceid=?',$exclude);
			if($this->_db->fetchOne($select)) return $str;
		}
		
		for($i=0;$i<100;$i++) {
			$uid = $i==0 ? $str : $str . '_' . $i;
			$select = $this -> _db->select()
						-> from('pw_resources',array('resourceuid'))
						-> where('resourceuid=?',$uid)
						-> where('isexist=?','yes');
			if(!$this->_db->fetchOne($select)) return $uid;
		}
		
		return $str . '_' . substr(md5(uniqid(rand(0,10000))),0,4);

	}
	
	/**
	 * МЕТОДЫ, ВОЗВРАЩАЮЩИЕ ДОПОЛНИТЕЛЬНУЮ ИНФОРМАЦИЮ О МАТЕРИАЛЕ
	 * МЕТОДЫ ОПРЕДЕЛЕНЫ КАК protected, ПОСКОЛЬКУ ВОЗВРАЩАЮТ ЧИСТЫЕ ЗНАЧЕНИЯ ДЛЯ ФОРМ
	 */
	
	/**
	 * Возвращает модифицированный массив информации о материале с дополнительной информацией
	 *
	 * @param array $resourceinfo Информация о материале @see self::getResourceInfo
	 * @return array
	 */
	
	protected function _getAdditionalInfo($resourceinfo) {
		
		/**
		 * Десериализация extradata
		 */
		
		if(!empty($resourceinfo['extradata'])) {
			$resourceinfo['extradata'] = Zend_Json::decode($resourceinfo['extradata']);
		}
		
		/**
		 * Десериализация metadata
		 */
		
		if(!empty($resourceinfo['metadata'])) {
			$resourceinfo['metadata'] = Zend_Json::decode($resourceinfo['metadata']);
		}
		
		/**
		 * Получение дополнительной информации в зависимости от значений в metadata
		 * @todo Не выбирать данные если о них нет информации в metadata поскольку это ведет к постоянной выборке images, files и prices
		 */
		
		$methods = array(
			'categories' => '_getCategories',
			'images' => '_getImages',
			'files' => '_getFiles',
			'tags' => '_getTags',
			'depends' => '_getDepends',
			'pairs' => '_getPairs',
			'structures' => '_getStructures',
		);
		
		foreach ($methods as $field=>$methodname) {
			if((isset($resourceinfo['metadata'][$field]) && $resourceinfo['metadata'][$field]>0) 
					|| !isset($resourceinfo['metadata'][$field])) {
				if($info = $this->$methodname($resourceinfo['resourceid'])) $resourceinfo[$field] = $info;
			}
		}
		
		/**
		 * Корректировка images to image
		 */
		
		if(isset($resourceinfo['images']) && count($resourceinfo['images'])===1) {
			$resourceinfo['image'] = $resourceinfo['images'][0]['mediaid'];
		}
		
		/**
		 * Корректировка files to file
		 */
		
		if(isset($resourceinfo['files']) && count($resourceinfo['files'])===1) {
			$resourceinfo['file'] = $resourceinfo['files'][0]['mediaid'];
		}
		
		/*if($info = $this->_getCategories($resourceinfo['resourceid']))
			$resourceinfo['categories'] = $info;*/
			
		return $resourceinfo;
		
	}
	
	/**
	 * Возвращает дополнительную информацию о разделах, в которых размещен материал
	 *
	 * @param int $resourceid
	 * @return array
	 */
	
	protected function _getCategories($resourceid) {
		
		$select = $this->_db->select()
				-> from('pw_resources_categories',array('categoryid'))
				-> where('resourceid=?',$resourceid);
					
		return $this->_db->fetchCol($select);
		
	}
	
	/**
	 * Возвращает дополнительную информацию об изображениях в материале
	 *
	 * @param int $resourceid
	 * @return array
	 */
	
	protected function _getImages($resourceid) {
		
		$select = $this->_db->select()
				-> from('pw_resources_media')
				-> where('resourceid=?',$resourceid)
				-> where('mediatype=?','image')
				-> order('sortorder');
					
		return $this->_db->fetchAll($select);
		
	}
	
	/**
	 * Возвращает дополнительную информацию о файлах в материале
	 *
	 * @param int $resourceid
	 * @return array
	 * @todo Работа с файлами повторяет работу с изображениями, мб имеет смысл их объеденить или сделать общий отдельный метод
	 */
	
	protected function _getFiles($resourceid) {
		
		$select = $this->_db->select()
				-> from('pw_resources_media')
				-> where('resourceid=?',$resourceid)
				-> where('mediatype!=?','image')
				-> order('sortorder');
					
		return $this->_db->fetchAll($select);
		
	}
	
	/**
	 * Возвращает дополнительную информацию о тегах в материале
	 *
	 * @param int $resourceid
	 * @return array
	 */
	
	protected function _getTags($resourceid) {
		
		$select = $this->_db->select()
				-> from(array('AA'=>'pw_resources_tags'),array('tagid'))
				-> join(array('BB'=>'pw_tags'),'AA.tagid=BB.tagid',array('tagname'))
				-> where('AA.resourceid=?',$resourceid)
				-> order('BB.tagname');
					
		return $this->_db->fetchAll($select);
		
	}
	
	/**
	 * Возвращает дополнительную информацию о связанных материалах в материале
	 *
	 * @param int $resourceid
	 * @return array
	 */
	
	protected function _getDepends($resourceid) {
		
		$select = $this->_db->select()
				-> from('pw_resources_depends',new Zend_Db_Expr('IF(resourceid1='.(int)$resourceid.',resourceid2,resourceid1) resourceid'))
				-> where('? IN(resourceid1,resourceid2)',$resourceid)
				-> where('resourceid1!=resourceid2')
				-> order('sortorder');
					
		return $this->_db->fetchCol($select);
		
	}
	
	/**
	 * Возвращает список произвольных полей в материале
	 *
	 * @param int $resourceid
	 * @return array
	 */
	
	protected function _getPairs($resourceid) {
		
		$select = $this->_db->select()
				-> from('pw_resources_pairs')
				-> where('resourceid=?',$resourceid)
				-> order('sortorder');
					
		return $this->_db->fetchAll($select);
		
	}
	
	/**
	 * Возвращает дополнительную информацию о структурах данных и значениях свойств в материале
	 *
	 * @param int $resourceid
	 * @return array
	 */
	
	protected function _getStructures($resourceid) {
		
		$select = $this->_db->select()
				-> from('pw_resources_structures')
				-> where('resourceid=?',$resourceid);
				
		$result = array();
					
		if($structures = $this->_db->fetchAll($select)) {
			foreach ($structures as $structure) {
				$result[$structure['structureid']][$structure['groupid']][$structure['propertyid']] = 
					!empty($structure['set_value']) 
						? explode(PHP_EOL,$structure['set_value']) : 
							$structure['text_value'] . $structure['int_value'] . $structure['float_value'];
			}
			return $result;
		}
		
		return false;
		
	}
	
	/**
	 * МЕТОДЫ, ФИЛЬТРУЮЩИЕ ДАННЫЕ МАТЕРИАЛА ПЕРЕД СОХРАНЕНИЕМ
	 * МЕТОДЫ ОПРЕДЕЛЕНЫ КАК protected, ПОСКОЛЬКУ ИСПОЛЬЗУЮТСЯ ПРИ СОХРАНЕНИИ МАТЕРИАЛА
	 * @todo Рассмотреть необходимость изменения видимости методов на public
	 */
	
	/**
	 * Предварительный фильтр данных материала
	 *
	 * @param array $resourceinfo Массив фильтруемых данных
	 * @param mixed $filters Массив или строка пользовательских методов
	 * @return array
	 */
	
	protected function _filterFormInfo($resourceinfo, $filters = null) {
		
		/**
		 * Системные фильтры
		 */
		
		/**
		 * Дата публикации при ее пустом значении
		 */
		
		if(!isset($resourceinfo['resourceid']) 
				&& (!isset($resourceinfo['pubdate']) || empty($resourceinfo['pubdate']))) 
						$resourceinfo['pubdate'] = date('d.m.Y H:i');
		
		/**
		 * Приводим даты к unix_timestamp
		 */
		
		if(isset($resourceinfo['pubdate']) && !preg_match('#^[0-9]+$#',$resourceinfo['pubdate']))
			$resourceinfo['pubdate'] = strtotime($resourceinfo['pubdate']);
			
		if(isset($resourceinfo['pubdate_from']) && !preg_match('#^[0-9]+$#',$resourceinfo['pubdate_from']))
			$resourceinfo['pubdate_from'] = strtotime($resourceinfo['pubdate_from']);
			
		if(isset($resourceinfo['pubdate_to']) && !preg_match('#^[0-9]+$#',$resourceinfo['pubdate_to']))
			$resourceinfo['pubdate_to'] = strtotime($resourceinfo['pubdate_to']);
		
		/**
		 * Идентификатор материала
		 */
		
		if(isset($resourceinfo['resourceuid'])) {
			$exclude = isset($resourceinfo['resourceid']) ? $resourceinfo['resourceid'] : null;
			$resourceinfo['resourceuid'] = empty($resourceinfo['resourceuid']) 
						? $this->getUidGenerated($resourceinfo['resourcename'],$exclude) 
							: $this->getUidGenerated($resourceinfo['resourceuid'],$exclude);
		}
		
		/**
		 * Одно изображение
		 */
		
		if(isset($resourceinfo['image'])) {
			if(is_array($resourceinfo['image'])) {
				$resourceinfo['images'] = $resourceinfo['image'];
			} else {
				$resourceinfo['images'] = array(array('mediaid'=>$resourceinfo['image']));
			}
		}
		
		/**
		 * Один файл
		 */
		
		if(isset($resourceinfo['file'])) {
			if(is_array($resourceinfo['file'])) {
				$resourceinfo['files'] = $resourceinfo['file'];
			} else {
				$resourceinfo['files'] = array(array('mediaid'=>$resourceinfo['file']));
			}
		}
		
		/**
		 * Добавляем идентификатор текущего модуля материалов
		 */
		
		$resourceinfo['moduleid'] = $this->_moduleid;
		
		/**
		 * Extradata
		 */
		
		if(isset($resourceinfo['extradata']) && is_array($resourceinfo['extradata'])) {
			$joined = join('',$resourceinfo['extradata']);
			if(empty($joined)) {
				$resourceinfo['extradata'] = null;
			} else {
				$resourceinfo['extradata'] = Zend_Json::encode($resourceinfo['extradata']);
			}
		}
		
		/**
		 * Пользовательские фильтры
		 */
		
		if(!is_null($filters)) {
			if(is_string($filters)) $filters = array($filters);
			if(is_array($filters)) {
				foreach ($filters as $methodname) {
					if(method_exists($this,$methodname)) {
						if(!$resourceinfo = $this->$methodname($resourceinfo)) {
							$this->_errors[] = 'Ошибка фильтрации данных в методе ' . $methodname;
							return false;
						}
					} else {
						$this->_errors[] = 'Не найден метод ' . $methodname;
						return false;
					}
				}
			} else {
				$this->_errors[] = 'Невозможно определить методы для фильтрации данных';
				return false;
			}
		}
		
		/**
		 * Metadata - просто считаем кол-во элементов в полях, где значения являются массивами
		 */
		
		$resourceinfo['metadata'] = array();
		foreach ($resourceinfo as $field=>$value) {
			if(is_array($value)) $resourceinfo['metadata'][$field] = count($value);
		}
		if(empty($resourceinfo['metadata'])) {
			$resourceinfo['metadata'] = null;
		} else {
			$resourceinfo['metadata'] = Zend_Json::encode($resourceinfo['metadata']);
		}
		
		return $resourceinfo;
		
	}
	
	/**
	 * МЕТОДЫ, СОХРАНЯЮЩИЕ ДОПОЛНИТЕЛЬНУЮ ИНФОРМАЦИЮ О МАТЕРИАЛЕ
	 * МЕТОДЫ ОПРЕДЕЛЕНЫ КАК protected, ПОСКОЛЬКУ ИСПОЛЬЗУЮТСЯ ПРИ СОХРАНЕНИИ МАТЕРИАЛА
	 * МЕТОДЫ ДОЛЖНЫ ВЫЗЫВАТЬСЯ В РАМКАХ SQL-ТРАНСАКЦИИ
	 * @todo Рассмотреть необходимость изменения видимости методов на public
	 */
	
	/**
	 * Сохраняет дополнительную информацию о материале
	 *
	 * @param array $resourceinfo Информация о материале @see self::saveResourceForm
	 * @param mixed $additionals Массив пользовательских методов
	 * @return array
	 */
	
	protected function _saveAdditionalInfo($resourceinfo, $additionals = null) {
		
		/**
		 * Системные методы
		 */
		
		$resourceinfo = $this->_saveCategories($resourceinfo);
		$resourceinfo = $this->_saveImages($resourceinfo);
		$resourceinfo = $this->_saveFiles($resourceinfo);
		$resourceinfo = $this->_saveTags($resourceinfo);
		$resourceinfo = $this->_saveDepends($resourceinfo);
		$resourceinfo = $this->_savePairs($resourceinfo);
		$resourceinfo = $this->_saveStructures($resourceinfo);
		
		/**
		 * Пользовательские методы
		 */
		
		if(!is_null($additionals)) {
			if(is_string($additionals)) $additionals = array($additionals);
			if(is_array($additionals)) {
				foreach ($additionals as $methodname) {
					if(method_exists($this,$methodname)) {
						if(!$resourceinfo = $this->$methodname($resourceinfo)) {
							$this->_errors[] = 'Ошибка сохранения в методе ' . $methodname;
							return false;
						}
					} else {
						$this->_errors[] = 'Не найден метод ' . $methodname;
						return false;
					}
				}
			} else {
				$this->_errors[] = 'Невозможно определить методы для обработки дополнительных данных';
				return false;
			}
		}
			
		return $resourceinfo;
		
	}
	
	/**
	 * Сохраняет связку материала с разделами
	 *
	 * @param array $data Массив данных о материале
	 * @return array
	 */
	
	protected function _saveCategories($data) {
		
		$this->_db->delete('pw_resources_categories','resourceid='.(int)$data['resourceid']);
			
		if(isset($data['categories']) && is_array($data['categories'])) {
			foreach ($data['categories'] as $categoryid) {
				if($categoryid>0) {
					$this->_db->insert('pw_resources_categories',array('resourceid'=>(int)$data['resourceid'],
																	   'categoryid'=>(int)$categoryid));
				}
			}
		}
		
		return $data;
		
	}
	
	/**
	 * Сохраняет связку материала с изображениями
	 *
	 * @param array $data Массив данных о материале
	 * @return array
	 */
	
	protected function _saveImages($data) {
		
		/**
		 * Отвязываем от материала все изображения
		 */
		
		$this->_db->delete('pw_resources_media','resourceid=' . (int)$data['resourceid'] . " AND mediatype='image'");
		
		/**
		 * Массив изображений
		 */
			
		if(isset($data['images'])) {
			
			/**
			 * Вставляем массив картинок
			 */
			
			if(is_array($data['images'])) {
				
				require_once 'Phorm/Media.php';
				$Media = new Phorm_Media();
				
				$images = $this->getFilteredValuesByTableCols('pw_resources_media',$data['images']);
					
				$itr = 0;
				foreach ($images as $image) {
					if($MediaInfo = $Media->getMediaInfo($image['mediaid'])) {
						$image['sortorder'] = $itr++;
						$image['mediatype'] = 'image';
						$image['resourceid'] = $data['resourceid'];
						$image['filepath'] = $MediaInfo['filepath'];
						$image['fileext'] = $MediaInfo['fileext'];
						$this->_db->insert('pw_resources_media',$image);
					}
				}
			
			}
					
		}
		
		return $data;
		
	}
	
	/**
	 * Сохраняет связку материала с файлами
	 *
	 * @param array $data Массив данных о материале
	 * @return array
	 * @todo Работа с файлами повторяет работу с изображениями, мб имеет смысл их объеденить или сделать общий отдельный метод
	 */
	
	protected function _saveFiles($data) {
		
		/**
		 * Отвязываем от материала все файлы
		 */
		
		$this->_db->delete('pw_resources_media','resourceid=' . (int)$data['resourceid'] . " AND mediatype!='image'");
		
		/**
		 * Массив файлов
		 */
			
		if(isset($data['files'])) {
			
			/**
			 * Вставляем массив файлов
			 */
			
			if(is_array($data['files'])) {
				
				require_once 'Phorm/Media.php';
				$Media = new Phorm_Media();
				
				$files = $this->getFilteredValuesByTableCols('pw_resources_media',$data['files']);
					
				$itr = 0;
				foreach ($files as $file) {
					if($MediaInfo = $Media->getMediaInfo($file['mediaid'])) {
						$file['sortorder'] = $itr++;
						$file['mediatype'] = $MediaInfo['mediatype'];
						$file['resourceid'] = $data['resourceid'];
						$file['filepath'] = $MediaInfo['filepath'];
						$file['fileext'] = $MediaInfo['fileext'];
						$this->_db->insert('pw_resources_media',$file);
					}
				}
			
			}
					
		}
		
		return $data;
		
	}
	
	/**
	 * Сохраняет связку материала с тегами
	 *
	 * @param array $data Массив данных о материале
	 * @return array
	 */
	
	protected function _saveTags($data) {
		
		$this->_db->delete('pw_resources_tags','resourceid='.(int)$data['resourceid']);
			
		if(isset($data['tags']) && is_array($data['tags'])) {
			foreach ($data['tags'] as $tag) {
				$select = $this->_db->select()
						-> from('pw_tags',array('tagid'))
						-> where('UCASE(tagname)=UCASE(?)',$tag['tagname']);
				if(!$tagid = $this->_db->fetchOne($select)) {
					$this->_db->insert('pw_tags',array('tagname'=>$tag['tagname']));
					$tagid = $this->_db->lastInsertId();
				}
				$this->_db->insert('pw_resources_tags',array('resourceid'=>(int)$data['resourceid'],'tagid'=>(int)$tagid));
			}
		}
		
		return $data;
		
	}
	
	/**
	 * Сохраняет связку материала с другими материалами
	 *
	 * @param array $data Массив данных о материале
	 * @return array
	 */
	
	protected function _saveDepends($data) {
		
		$this->_db->delete('pw_resources_depends',(int)$data['resourceid'].' IN(resourceid1,resourceid2)');
			
		if(isset($data['depends']) && is_array($data['depends'])) {
			foreach ($data['depends'] as $sortorder=>$resourceid) {
				if($data['resourceid']===$resourceid) continue;
				$this->_db->insert('pw_resources_depends',array('resourceid1'=>$data['resourceid'],
																'resourceid2'=>$resourceid,
																'sortorder'=>$sortorder));
			}
		}
		
		return $data;
		
	}
	
	/**
	 * Сохраняет связку материала с произволными полями
	 *
	 * @param array $data Массив данных о материале
	 * @return array
	 */
	
	protected function _savePairs($data) {
			
		$this->_db->delete('pw_resources_pairs','resourceid='.(int)$data['resourceid']);
			
		if(isset($data['pairs']) && is_array($data['pairs'])) {
			$itr = 0;
			foreach ($data['pairs'] as $pair) {
				$pair['sortorder'] = $itr++;
				$pair['resourceid'] = $data['resourceid'];
				$this->_db->insert('pw_resources_pairs',$pair);
			}
		}
		
		return $data;
		
	}
	
	/**
	 * Сохраняет связку материала со значениями в структурах данных
	 *
	 * @param array $data Массив данных о материале
	 * @return array
	 */
	
	protected function _saveStructures($data) {
		
		$this->_db->delete('pw_resources_structures','resourceid='.(int)$data['resourceid']);
		
		if(isset($data['structures']) && is_array($data['structures'])) {
				
			foreach ($data['structures'] as $structureid=>$group) {
				if(is_array($group)) {
					foreach ($group as $groupid=>$property) {
						if(is_array($property)) {
							foreach ($property as $propertyid=>$value) {
								$structuredata = array(
									'resourceid' => $data['resourceid'],
									'structureid' => $structureid,
									'groupid' => $groupid,
									'propertyid' => $propertyid
								);
								if(is_array($value)) {
									$structuredata['set_value'] = join(PHP_EOL,$value);
								} elseif (preg_match('#^[0-9]+$#',$value)) {
									$structuredata['int_value'] = (int)$value;
								} elseif (is_float($value)) {
									$structuredata['float_value'] = (double)$value;
								} else {
									$structuredata['text_value'] = $value;
								}
								$this->_db->insert('pw_resources_structures',$structuredata);
							}
						}
					}
				}
			}
				
		}
		
		return $data;
		
	}
	
}